#!/bin/bash
# 作者: 阿杰
# 描述: Redis 集群部署脚本  
# 版本: v1.0
# 日期: 2024-07-03
# 使用方式:
#   集群方式:
#     bash redis.sh --address 127.0.0.1 --mode cluster --port 7001
#     初始化集群
#     bash redis.sh --address 127.0.0.1  --mode cluster --port 7006 --address_list 127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003,127.0.0.1:7004,127.0.0.1:7005,127.0.0.1:7006
#   单机部署
#   bash redis.sh --address 127.0.0.1
# 多节点可采用for循环将脚本传输到指定节点进行部署


# Log
log::err() {
  printf "[$(date +'%Y-%m-%dT%H:%M:%S.%N%z')]: \033[31mERROR: \033[0m$@\n"
  exit 1
}

log::info() {
  printf "[$(date +'%Y-%m-%dT%H:%M:%S.%N%z')]: \033[32mINFO: \033[0m$@\n"
}

log::warning() {
  printf "[$(date +'%Y-%m-%dT%H:%M:%S.%N%z')]: \033[33mWARNING: \033[0m$@\n"
}




# Redis 初始化
function redis::init() {
  WORKER="/data/redis_$REDIS_PORT/"
  RedisConfigPath="${WORKER}config"
  RedisDataPath="${WORKER}data"
  RedisLogPath="${WORKER}log"

  # 配置文件
  DockerComposeFile=${WORKER}docker-compose.yml
  RedisConfigFile="${RedisConfigPath}/redis.conf"
  array_path=("$RedisConfigPath" "$RedisDataPath" "$RedisLogPath")
  for i in ${array_path[@]};do
    utils::isDirExtist $i
    chown systemd-coredump.root $i
    if [ $? -ne 0 ];then
      log::err "path: $i 设置属主失败"
    fi
  done
}

# 创建 Redis 配置文件
function redis::createConfig() {
  local config="bind 0.0.0.0
port ${REDIS_PORT}
tcp-backlog 511
timeout 0
tcp-keepalive 300
daemonize no
pidfile /tmp/redis.pid
loglevel notice
logfile /log/redis.log
databases 16
always-show-logo no
set-proc-title yes
proc-title-template \"{title} {listen-addr} {server-mode}\"
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
rdb-del-sync-files no
dir /data
replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-diskless-load disabled
repl-disable-tcp-nodelay no
replica-priority 100
acllog-max-len 128
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no
lazyfree-lazy-user-del no
lazyfree-lazy-user-flush no
oom-score-adj no
oom-score-adj-values 0 200 800
disable-thp yes
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
aof-use-rdb-preamble yes
lua-time-limit 5000
cluster-enabled yes
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events \"\"
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
stream-node-max-bytes 4096
stream-node-max-entries 100
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
dynamic-hz yes
aof-rewrite-incremental-fsync yes
rdb-save-incremental-fsync yes
requirepass 'sd2DawWDS#0aRP82FvJ'
masterauth 'sd2DawWDS#0aRP82FvJ'"
  if [ "$INSTALL_MODE" == "cluster" ];then
    local config+=$'\n'"cluster-enabled yes
cluster-config-file /data/nodes.conf
#填写当前节点IP地址
cluster-announce-ip $ADDRESS
cluster-announce-port $REDIS_PORT
cluster-announce-bus-port 1${REDIS_PORT}
slowlog-log-slower-than 10000"
  fi

  echo "$config" > ${RedisConfigFile}
  if [ $? -ne 0 ];then
    log::err "生成Redis配置文件失败"
  fi
  log::info "生成Redis配置文件"
}

# 创建redis docker-compose 文件
function docker::createRedisDockerComposeFile() {
  local conf="version: \"3\"
services:
  redis:
    container_name: redis_$REDIS_PORT
    image: $IMAGE_NAME
    restart: always
    ports:
      - $REDIS_PORT:$REDIS_PORT
      - CLUSTER_PORT:CLUSTER_PORT
    environment:
      - TZ=Asia/Shanghai
    volumes:
      - /etc/localtime:/etc/localtime
      - $RedisConfigPath:/opt/config:ro
      - $RedisDataPath:/data:rw
      - $RedisLogPath:/log:rw
    command: redis-server /opt/config/redis.conf"
  if [ "$INSTALL_MODE" == "redis" ];then
    local conf=$(echo "$conf" | sed -e "/- CLUSTER_PORT:CLUSTER_PORT/d")
  else
    local conf=$(echo "$conf" | sed -e "s/- CLUSTER_PORT:CLUSTER_PORT/- 1${REDIS_PORT}:1${REDIS_PORT}/g")
  fi

  echo "$conf"> ${DockerComposeFile}
  if [ $? -ne 0 ];then
    log::err "file: ${DockerComposeFile} 创建docker yaml 文件失败"
  fi
  log::info "file: ${DockerComposeFile} 创建docker yaml 文件成功"    
}

function redis::start() {
  docker-compose -f ${DockerComposeFile} up -d > /dev/null 2>&1
  if [ $? -ne 0 ];then
    log::err "redis 启动失败"
  fi
  log::info "redis 启动成功"
}

function redis::initCluster() {
  if [ -n "$ADDRESS_LIST" ];then

      # 检查expect是否存在
      which expect > /dev/null 2>&1
      if [ $? -ne 0 ];then
        yum -y install expect > /dev/null 2>&1
        if [ $? -ne 0 ];then
          log::err "yum 安装 expect 失败"
        fi
        log:info "yum 安装 expect 成功"
      fi
    local ADDRESS=$(echo "$ADDRESS_LIST" | tr "," " " )
    /usr/bin/expect <<EOF
    spawn docker exec -i redis_$REDIS_PORT /bin/bash -c "redis-cli -a 'sd2DawWDS#0aRP82FvJ' --cluster create  $ADDRESS --cluster-replicas 1"
    expect {
        "(type 'yes' to accept):" { send "yes\r"; exp_continue }
    }
EOF
  fi
}


# 安装Redis
function redis::install() {

  # 配置初始化
  redis::init
  # 创建配置文件
  redis::createConfig
  # 创建docker-compose文件
  docker::createRedisDockerComposeFile
  # 启动redis
  redis::start
  # 集群初始化
  redis::initCluster
}

function checkParm() {

  if [ -z "$ADDRESS" ];then
    log::warning "address 参数不可为空"
    usage
  fi
  
  if [ -z "$(echo $INSTALL_MODE | egrep 'cluster|redis')" ];then
    log::warning "mode 参数不可为空"
    usage
  fi

  if [ -z "${REDIS_PORT}" ];then
    REDIS_PORT=7001
  fi

  if [ -z "${IMAGE_NAME}" ];then
    IMAGE_NAME=redis:latest
  fi

}

#判断目录是否存在不存在则创建
function utils::isDirExtist() {
  if [ ! -d $1 ];then
    mkdir -p $1
    if [ $? -ne 0 ];then
        log::err "path: $1 ,目录创建失败"
    fi
  fi
}


function initUsage() {
  # 参数初始化
  if [[ $# -eq 0 ]];then
    usage
  fi
  while [[ $# -gt 0 ]]; do
    case $1 in
      --address)
        ADDRESS=$2
        shift
        shift
      ;;
      --mode)
        INSTALL_MODE=$2
        shift
        shift
      ;;
      --port)
        REDIS_PORT=$2
        shift
        shift
      ;;
      --address_list)
        ADDRESS_LIST=$2
        shift
        shift
      ;;
      --image_name)
        IMAGE_NAME=$2
        shift
        shift
      ;;
      --help|*)
        usage
      ;;
    esac
  done
}

function usage() {
  echo "Usage: $0
        --address [127.0.0.1] 填写IP地址
        --mode [cluster|redis] 集群模式或单机模式
        --port 7000 redis端口号
        --address_list 127.0.0.1:7001,127.0.0.1:7002 初始化集群，执行集群模式交互式脚本
        "
  exit 1
}


function main() {
  # 参数初始化
  initUsage $@
  # 检查参数
  checkParm
  # 安装 redis
  redis::install
}

main $@
